/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package simco.framework.gui.swing.comp;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import simco.framework.gui.graph.ConnectingLine;
import simco.framework.gui.graph.base.GraphObject;
import simco.framework.gui.graph.iface.Drawable;
import simco.framework.gui.graph.iface.Movable;
import simco.framework.gui.graph.iface.Selectable;
import simco.framework.gui.services.ChangeEventSupporter;
import simco.framework.gui.services.CoreInformationProvider;
import simco.framework.gui.services.data.GraphModel;
import simco.framework.gui.services.data.InfoModel;

/**
 * Tato trida reprezentuje panel, na ktery jsou vykreslovany
 * simulavane komponenty, jejich vazby a rozhrani. 
 *
 * @author Matej Prokop, mythge@students.zcu.cz
 */
public class CanvasPanel extends JPanel implements ChangeListener
{
	/**
	 * Seznam vykreslovanych objektu a vazeb
	 */
    private List<Drawable> drawableList;
    
    /**
     *  Seznam vsech oznacitelnych objektu na platne
     */
    private List<Selectable> selectableList;
    
    /**
     * Aktualne oznaceny objekt
     */
    private Selectable selected;
    
    // injected by Spring DM
    /**
     * Datovy model objektu vykreslovanych na platno
     */
    private GraphModel graphModel;
    
    // injected by Spring DM
    /**
     * Datovy model, ve kterem jsou uchovany informace
     *  o prave vybranem objektu
     */
    private InfoModel infoModel;
    
    /**
     * Reference na objekt generujici udalosti pri
     * zmene vybraneho objektu
     */
    private SelectionChangeAnnouncer selectionRes;
    
    /**
     * Souradnice X pri poslednim kliku na platno
     */
    private int lastX;
    
    /**
     * Souradnice X pri poslednim kliku na platno
     */
    private int lastY;

    /**
     * Slouzi pro redukci poctu prekreslovani
     * GUI pri posunu objektu po platne. 
     * Pozn: Pro pouziti je nutne odkomentovat
     * kod v metode {@link CanvasPanel#draggedAt(int, int)}
     */
    private boolean repaintsFilter = true;
    
    /**
     * Velikost okraje platna 
     */
    private final int INSETS_SIZE = 15;
    
    /**
     * X-ova souradnice bodu, ktery se nachazi nejdele
     * na ose X ze vsech vizualizovanych bodu na platne.
     */
    private int objectMaxY;
    
    /**
     * Y-ova souradnice bodu, ktery se nachazi nejdele
     * na ose Y ze vsech vizualizovanych bodu na platne.
     */
    private int objectMaxX;
    
    /**
     * Logicka metoda, zda-li byl jsi jiz model
     * grafu jednou vykreslne.
     */
    private boolean modelPublished = false;

    /**
     * Vytvori novy panel reprezentujici platno pro
     * vizualiace grafu simulovanych komponent.
     * 
     * @param model datovy model objektu vykreslovanych na platno 
     * @param infoModel datovy model, ve kterem jsou uchovany informace
     *  o prave vybranem objektu
     */
    public CanvasPanel(GraphModel model, InfoModel infoModel)
    {        
    	this.infoModel = infoModel;
    	this.graphModel = model;
    	this.graphModel.addChangeListener(this);
        setBackground(Color.WHITE);

        this.selectionRes = new SelectionChangeAnnouncer();
        
        this.selectableList = new ArrayList<Selectable>();
        this.drawableList = new ArrayList<Drawable>();
        
        this.objectMaxX = this.objectMaxY = 0;
        
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                clickedkAt(e.getX(), e.getY());
            }
        });

        addMouseMotionListener(new MouseAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                draggedAt(e.getX(), e.getY());
            }
        });
    }    

	@Override
	public void stateChanged(ChangeEvent e) 
    {
		drawableList.clear();
		selectableList.clear();
		
        for(ConnectingLine cl : graphModel.getConnLines())
        {
            drawableList.add(cl);
        }
        
        int yMax = 0;
        int xMax = 0;
        
        int yMin = Integer.MAX_VALUE;
        int xMin = Integer.MAX_VALUE;
        
        for(GraphObject go : graphModel.getGraphObjects())
        {
            drawableList.add(go);
            selectableList.add(go);
            
            yMax = Math.max(yMax, go.getMaxY());
            xMax = Math.max(xMax, go.getMaxX());
            yMin = Math.min(yMin, go.getY());
            xMin = Math.min(xMin, go.getX());
        }
        
        for(GraphObject go : graphModel.getGraphObjects())
        {                        
        	go.setPosition(go.getX() - xMin + INSETS_SIZE,
        			go.getY() - yMin + INSETS_SIZE);
        }
               
        for(ConnectingLine cl : graphModel.getConnLines())
        {
            selectableList.add(cl);
        }   	
        
        /*
         * Nastavi velikost platna podle prvku na nem
         * vykreslovanych. Platno je vlozene ve scroll
         * panelu. Diky tomu pujde se k objektum mimo
         * platno proscrolovat. 
         */
        this.objectMaxX = xMax - xMin + 2 * INSETS_SIZE;
        this.objectMaxY = yMax - yMin + 2 * INSETS_SIZE;
        this.setPreferredSize(new Dimension(objectMaxX, objectMaxY));
        
        if(!modelPublished)
        {
        	modelPublished = true;
        }
        else
        {
        	this.revalidate();
        }
        
        this.repaint();
	}
    
    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        
        Graphics2D g2 = (Graphics2D) g;
        
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        
        for(Drawable d : this.drawableList)
        {
        	d.drawObject(g2);
        }
    }

    /**
     * Metoda zajistujici presunuti objektu po platne po
     * zadragovani mysi.
     * 
     * @param x x-ova souradnice na kterou bylo mysi zadragovano
     * @param y y-ova souradnice na kterou bylo mysi zadragovano
     */
    private void draggedAt(int x, int y)
    {
        if(selected == null)
            return;

        if(selected instanceof Movable == false)
        {
        	return;
        }

// 		  Pod odkomentovani kodu nize bude zapnut filter prekreslovani GUI:        
//        if(repaintsFilter)
//        {
//            // kazdy druhy event zahodim, pro zmenseni zateze vykreslovani
//            repaintsFilter = false;
//            return;
//        }
//        else
//        {
//            repaintsFilter = true;
//        }

        int difX = x - lastX;
        int difY = y - lastY;

        if(difX == 0 && difY == 0) return;

        lastX = x;
        lastY = y;
        
        Movable movedObject = (Movable) selected;
        
        int newX = difX + movedObject.getX();
        int newY = difY + movedObject.getY();
        
        if(newX < INSETS_SIZE || newY < INSETS_SIZE)
        {
        	return;
        }
        
        movedObject.setPosition(difX + movedObject.getX(),
        		difY + movedObject.getY());
        
        boolean canvasSizeChanged = false;
        // testuje jestli je potreba zvetsit rozmer platna (vytvorit scroll bar):
        if(movedObject.getMaxX() + INSETS_SIZE > objectMaxX )
        {
        	this.objectMaxX = movedObject.getMaxX() + INSETS_SIZE;
        	this.graphModel.setCanvasHeight(this.objectMaxX - 2 * INSETS_SIZE );
        	canvasSizeChanged = true;
        }
        
        if(movedObject.getMaxY() + INSETS_SIZE > objectMaxY )
        {
        	this.objectMaxY = movedObject.getMaxY() + INSETS_SIZE;
        	this.graphModel.setCanvasHeight(this.objectMaxY - 2 * INSETS_SIZE );
        	canvasSizeChanged = true;        	
        }
        
        if(canvasSizeChanged)
        {
        	this.setPreferredSize(new Dimension(objectMaxX, objectMaxY));
        	this.revalidate();
        }
        else
        {
        	this.repaint();       
        	
        }	
    }

    /**
     * Metoda zajistujici oznacovani objektu na platne po
     * kliknut mysi na platno.
     * 
     * @param x x-ova souradnice na kterou bylo mysi kliknuto
     * @param y y-ova souradnice na kterou bylo mysi kliknuto
     */
    private void clickedkAt(int x, int y)
    {
    	Selectable currentSel = null;
    	
        lastX = x;
        lastY = y;

        for(Selectable s : selectableList)
        {
            if(s.isInsideObject(x, y))
            {
            	s.setSelected(true);
                currentSel = s;                
                break;
            }
        }
        
        if(currentSel == selected)
        {
        	return;
        }

        if(selected != null)
        {
        	selected.setSelected(false);        	
        }
        
        selected = currentSel;        
        selectionRes.newSelection(currentSel, infoModel);
        
        repaint();
    }
    
    /**
     * Nastavuje poskytovatele informaci o vybranem objektu, resp.
     * objekt, kteremu budou predavany udalosti o zmene
     * vybraneho objektu na platne
     * 
     * @param informationProvider poskytovatel informaci o vybranem objektu
     */
    public void setInformationProvider(CoreInformationProvider informationProvider)
    {
    	selectionRes.addChangeListener(informationProvider);
    }
    
    /**
     * Trida zajistujici informovani ostatnich casti aplikace
     * o zmene vybraneho objektu prostrednictvim udalosti.
     * 
     * @author Matej Prokop, mythge@students.zcu.cz
     */
    class SelectionChangeAnnouncer extends ChangeEventSupporter
    {    	
    	/**
    	 * Vyvola udalost informujici posluchace o zmene
    	 * vybraneho objektu
    	 * @param s vybrany objektu 
    	 * @param model datovy model, ve kterem jsou uchovany informace
    	 * o prave vybranem objektu
    	 */
    	public void newSelection(Selectable s, InfoModel model)
    	{
    		model.setSelectedObject(s);
    		fireStateChanged();
    	}
    }

    /**
     * Vraci novou instanci component listeneru.
     * @return nova instance component listeneru
     */
    public ComponentListener getResizeListener()
    {
    	return new MyComponentListener();
    }    
    
    /**
     * Tato trida definuje component listener, ktery
     * reaguje na udalosti zvetseni komponenty.
     * 
     * @author Matej Prokop, myhge@students.zcu.cz
     */
    class MyComponentListener implements ComponentListener
    {
		@Override
		public void componentHidden(ComponentEvent e) 
		{
		}

		@Override
		public void componentMoved(ComponentEvent e) 
		{	
		}

		@Override
		public void componentResized(ComponentEvent e) 
		{	
			graphModel.setCanvasHeight(((JScrollPane) e.getSource()).getHeight() - 2 * INSETS_SIZE);
			graphModel.setCanvasWidth(((JScrollPane) e.getSource()).getWidth() - 2 * INSETS_SIZE);
		}

		@Override
		public void componentShown(ComponentEvent e) 
		{
		}    	
    }

}

